﻿//-----------------------------------------------------------------------
// <copyright file="DiagnosticsService.cs" company="CodePlex">
//     Copyright (c) Doug Holland, CodePlex. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace CodePlex.Diagnostics.Services
{
    using System;

    using System.Collections;
    using System.Collections.Generic;

    using System.Data;
    using System.Data.SqlClient;

    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    using System.Linq;

    using System.Reflection;

    using System.ServiceModel;
    using System.ServiceModel.Activation;

    using System.Xml;
    using System.Xml.Linq;

    using CodePlex.Diagnostics.Contracts;

    using CodePlex.Diagnostics.Model;

    /// <summary>
    /// Provides the default implementation of the <see cref="T:CodePlex.Diagnostics.Contracts.IDiagnosticsService"/> service contract.
    /// </summary>
    [ServiceKnownType(typeof(MethodBase))]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public sealed class DiagnosticsService : IDiagnosticsService
    {
        #region Guid IDiagnosticsService.PublishException(ExceptionDataContract ex, Dictionary<string, string> dictionary, XElement element, IIdentityDataContract identity)

        /// <summary>
        /// Publishes the exception information contained within the specified <see cref="T:CodePlex.Diagnostics.Contracts.ExceptionDataContract"/> to the database.
        /// </summary>
        /// <param name="contract">The <see cref="T:CodePlex.Diagnostics.Contracts.ExceptionDataContract"/> containing the exception information to be published to the database.</param>
        /// <param name="dictionary">A <see cref="T:System.Collections.Generic.Dictionary`2"/> containing name value pairs associated with this exception.</param>
        /// <param name="element">A <see cref="T:System.Xml.Linq.XElement"/> representing an xml element to be associated with this exception.</param>
        /// <param name="identity">The <see cref="T:CodePlex.Diagnostics.Contracts.IIdentityDataContract"/> containing the identity information to be published to the database.</param>
        /// <returns>A <see cref="T:System.Guid"/> representing the unique publication identifier for the exception.</returns>
        Guid IDiagnosticsService.PublishException(ExceptionDataContract contract, Dictionary<string, string> dictionary, XElement element, IIdentityDataContract identity)
        {
            using (EntityContext entities = new EntityContext())
            {
                // establish a new ExceptionEntity class based upon the current exception, identity, and publication Id (Guid.NewGuid()).
                ExceptionEntity entity = new ExceptionEntity(contract, identity, Guid.NewGuid());

                // update the exception entity set with the new exception.
                entities.AddToExceptionEntitySet(entity);

                try
                {
                    // update the exceptions within the database accordingly.
                    entities.SaveChanges();
                }
                // HACK: occassionally an OptimisticConcurrencyException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (OptimisticConcurrencyException) { }
                // HACK: occassionally an InvalidOperationException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (InvalidOperationException) { }

                // return the publication Id for this exception and its inner exceptions.
                return entity.PublicationId;
            }
        }

        #endregion

        #region Guid IDiagnosticsService.Log(LogEntryDataContract contract, Dictionary<string, string> dictionary, XElement element, IIdentityDataContract identity)

        /// <summary>
        /// Writes the <see cref="T:System.String"/> using the configured logging provider.
        /// </summary>
        /// <param name="contract">The <see cref="T:CodePlex.Diagnostics.Contracts.LogEntryDataContract"/> containing the log entry information to be published to the database.</param>
        /// <param name="dictionary">A <see cref="T:System.Collections.Generic.Dictionary`2"/> containing name value pairs associated with this log entry.</param>
        /// <param name="element">A <see cref="T:System.Xml.Linq.XElement"/> representing an xml element to be associated with this log entry.</param>
        /// <param name="identity">The <see cref="T:CodePlex.Diagnostics.Contracts.IIdentityDataContract"/> containing the identity information to be published to the database.</param>
        /// <returns>A <see cref="T:System.Guid"/> representing the unique publication identifier for the log entry.</returns>
        Guid IDiagnosticsService.Log(LogEntryDataContract contract, Dictionary<string, string> dictionary, XElement element, IIdentityDataContract identity)
        {
            using (EntityContext entities = new EntityContext())
            {
                LogEntryEntity entity = new LogEntryEntity(contract, identity);

                // update the log entry entity set with the new log entry.
                entities.AddToLogEntryEntitySet(entity);

                try
                {
                    // update the log entries within the database accordingly.
                    entities.SaveChanges();
                }
                // HACK: occassionally an OptimisticConcurrencyException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (OptimisticConcurrencyException) { }
                // HACK: occassionally an InvalidOperationException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (InvalidOperationException) { }

                // return the publication Id for this log entry.
                return entity.PublicationId;
            }
       }

        #endregion

        #region void IDiagnosticsService.UpdateProcessExitInformation(int processId, DateTime startTime, DateTime exitTime, int exitCode)

        /// <summary>
        /// Updates details about how and when the <see cref="T:System.Diagnostics.Process"/> exited within the CodePlex.Diagnostics database.
        /// </summary>
        /// <param name="processId">The process Id representing the <see cref="T:System.Diagnostics.Process"/> for which details about how and when it exited should be published.</param>
        /// <param name="startTime">The process start time.</param>
        /// <param name="exitTime">The process exit time.</param>
        /// <param name="exitCode">The process exit code.</param>
        /// <remarks>
        /// <para>Process exit details are only published if exceptions were published or log entries were written while the process was alive.</para>
        /// <para>The process is identified by both the process Id and the process start time ensuring that an incorrect process is not identified if the operating system used the same process Id again.</para>
        /// </remarks>
        void IDiagnosticsService.UpdateProcessExitInformation(int processId, DateTime startTime, DateTime exitTime, int exitCode)
        {
            using (EntityContext entities = new EntityContext())
            {
                // search for processes within the database with the same process Id and start time.
                var processes = entities.ProcessEntitySet
                    .Where(p => p.Id == processId && p.StartTime == startTime);

                // update the ExitCode, ExitTime, and HasExited properties for all processes found.
                foreach (var process in processes)
                {
                    process.ExitCode = exitCode;
                    process.ExitTime = exitTime;
                    process.HasExited = true;
                }

                try
                {
                    // update the processes within the database accordingly.
                    entities.SaveChanges();
                }
                // HACK: occassionally an OptimisticConcurrencyException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (OptimisticConcurrencyException) { }
                // HACK: occassionally an InvalidOperationException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (InvalidOperationException) { }
            }
        }

        #endregion

        #region void IDiagnosticsService.RegisterTeamFoundationServer(TeamFoundationServerDataContract contract)

        /// <summary>
        /// Registers the Team Foundation Server specified by the supplied <see cref="T:Uri"/> within the CodePlex.Diagnostics database.
        /// </summary>
        /// <param name="contract">The <see cref="T:CodePlex.Diagnostics.Contracts.TeamFoundationServerDataContract"/> representing the Team Foundation Server to be registered within the database.</param>
        void IDiagnosticsService.RegisterTeamFoundationServer(TeamFoundationServerDataContract contract)
        {
            using (EntityContext entities = new EntityContext())
            {
                // establish a new TeamFoundationServerEntity class based upon the specified TeamFoundationServerDataContract.
                TeamFoundationServerEntity entity = new TeamFoundationServerEntity(contract);

                // update the Team Foundation Server entity set with the new exception.
                entities.AddToTeamFoundationServerEntitySet(entity);

                try
                {
                    // update the Team Foundation Servers within the database accordingly.
                    entities.SaveChanges();
                }
                // HACK: occassionally an OptimisticConcurrencyException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (OptimisticConcurrencyException) { }
                // HACK: occassionally an InvalidOperationException occurs when calling into the Entity Framework and the ignoring of this exception will be refactored away soon.
                catch (InvalidOperationException) { }
            }
        }

        #endregion
    }
}
